package com.simon.study.algorithm.thread;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 *
 */
public class AlternatelyExecute {

    public static void main(String[] args) {
//         execute(alternatelyPrintInMultipleThreadWithLock());
//         execute(alternatelyPrintInMultipleThreadWithAwait());
//         execute(alternatelyPrintInMultipleThreadWithWait());
//         execute(alternatelyPrintInMultipleThreadWithSynchronized());
        execute(alternatelyPrintInTwoThreadWithSynchronized());
    }

    public static Runnable[] alternatelyPrintInMultipleThreadWithLock(){
        int tc = 3;
        ReentrantLock lock = new ReentrantLock(true);
        Monitor monitor    = new Monitor();

        Runnable[] runs = new Runnable[tc];
        for (int i = 0; i < runs.length; i++) {
            final int n = i;
            runs[i] = () -> {
                while (true){
                    try {
                        lock.lock();
                        if((monitor.mc % runs.length) == n){
                            System.out.println(Thread.currentThread().getName() + " " + (n+1) + " at run.");
                            monitor.mc ++ ;
                            Thread.sleep(300);
                        }
                    } catch (Exception e){
                        e.printStackTrace();
                        break;
                    }finally {
                        lock.unlock();
                    }
                }
            };
        }
        return runs;
    }

    public static Runnable[] alternatelyPrintInMultipleThreadWithAwait(){
        int rc = 3;

        Runnable[] runs = new Runnable[rc];
        ReentrantLock lock = new ReentrantLock();
        Condition cdt      = lock.newCondition();
        Monitor  monitor   = new Monitor();

        for (int i = 0; i < runs.length; i++) {
            final int n = i;
            runs[i] = () -> {
                while (true){
                    try {
                        lock.lock();
                        if((monitor.mc % runs.length) == n){
                            System.out.println( Thread.currentThread().getName() + " " + (n+1) + " at run." );
                            monitor.mc ++;

                            Thread.sleep(300 );


                            int an = lock.getWaitQueueLength(cdt);
                            System.out.println( "\t Await number: " + an );
                            if(an > 0) { cdt.signalAll(); }

                            cdt.await();
                        }

                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        lock.unlock();
                    }
                }
            };
        }
        return runs;
    }

    public static Runnable[] alternatelyPrintInMultipleThreadWithWait(){
        int rc = 3;

        Runnable[] runs = new Runnable[rc];
        Monitor monitor = new Monitor();

        for (int i = 0; i < runs.length; i++) {
            final int n = i;
            runs[i] = () -> {
                while (true){
                    synchronized (monitor){
                        monitor.notifyAll();

                        if((monitor.mc % runs.length) == n){
                            System.out.println(Thread.currentThread().getName() + " " + (n+1) + " at run.");
                            monitor.mc ++;

                            try {
                                Thread.sleep(300);
                            }catch (Exception e){
                                e.printStackTrace();
                                break;
                            }
                        }

                        try {
                            monitor.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            break;
                        }
                    }
                }
            };
        }
        return runs;
    }

    public static Runnable[] alternatelyPrintInMultipleThreadWithSynchronized(){
        int tc              = 3;
        Runnable[] runs     = new Runnable[tc];

        Monitor monitor     = new Monitor();

        for (int i = 0; i < runs.length; i++) {
            final int n = i;
            runs[i] = () -> {
                while (true){
                    synchronized (monitor){
                        if((monitor.mc % runs.length) == n){
                            System.out.println(Thread.currentThread().getName() + " " + (n+1) + " at run.");
                            monitor.mc ++;

                            try {
                                Thread.sleep(300);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                                break;
                            }
                        }
                    }
                }
            };
        }

        return runs;
    }

    public static Runnable[] alternatelyPrintInTwoThreadWithSynchronized(){
        int tc              = 2;
        Runnable[] runs     = new Runnable[tc];
        Monitor monitor     = new Monitor();

        for (int i = 0; i < runs.length; i++) {
            final int p = i;
            runs[i] = () -> {
                while (true){
                    synchronized (monitor){
                        if(monitor.mc ==  p){
                            System.out.println(Thread.currentThread().getName() + " " + (p+1) + " at run.");
                            monitor.mc =  1-p;

                            try {
                                Thread.sleep(300);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                                break;
                            }
                        }
                    }
                }
            };
        }
        return runs;
    }

    public static class Monitor{ public volatile int mc = 0;}

    public static void execute(Runnable[] runs){
        for (int i = 0; i < runs.length; i++) {
            new Thread(runs[i]).start();
        }
    }
}
